<template>
  <div class="picker">
    <input
      :class="schema.commonFormControlClass"
      :placeholder="placeholder"
      :readonly="true"
      :disabled="disabled"
      @click="globalClickHandler"
      @focus="handleFocus"
      @keydown="handleKeydown"
      :value="displayValue"
      @input="displayValue = $event.target.value"
      :validateEvent="false"
      ref="reference">
  </div>
</template>

<script>
import Vue from 'vue'
import {DateUtils} from './utils'
import Utils from '../../utils'

const DEFAULT_FORMATS = {
  date: 'yyyy-MM-dd',
  month: 'yyyy-MM',
  datetime: 'yyyy-MM-dd HH:mm:ss',
  time: 'HH:mm:ss',
  week: 'yyyywWW',
  timerange: 'HH:mm:ss',
  daterange: 'yyyy-MM-dd',
  datetimerange: 'yyyy-MM-dd HH:mm:ss',
  year: 'yyyy'
}
const HAVE_TRIGGER_TYPES = [
  'date',
  'datetime',
  'time',
  'time-select',
  'week',
  'month',
  'year',
  'daterange',
  'timerange',
  'datetimerange'
]
const DATE_FORMATTER = function (value, format) {
  return DateUtils.formatDate(value, format)
}
const DATE_PARSER = function (text, format) {
  return DateUtils.parseDate(text, format)
}
const RANGE_FORMATTER = function (value, format, separator) {
  if (Array.isArray(value) && value.length === 2) {
    const start = value[0]
    const end = value[1]

    if (start && end) {
      return DateUtils.formatDate(start, format) + separator + DateUtils.formatDate(end, format)
    }
  }
  return ''
}
const RANGE_PARSER = function (text, format, separator) {
  const array = text.split(separator)
  if (array.length === 2) {
    const range1 = array[0]
    const range2 = array[1]

    return [DateUtils.parseDate(range1, format), DateUtils.parseDate(range2, format)]
  }
  return []
}
const TYPE_VALUE_RESOLVER_MAP = {
  default: {
    formatter (value) {
      if (!value) return ''
      return '' + value
    },
    parser (text) {
      if (text === undefined || text === '') return null
      return text
    }
  },
  week: {
    formatter (value, format) {
      let date = DateUtils.formatDate(value, format)
      const week = DateUtils.getWeekNumber(value)

      date = /WW/.test(date)
            ? date.replace(/WW/, week < 10 ? '0' + week : week)
            : date.replace(/W/, week)
      return date
    },
    parser (text) {
      const array = (text || '').split('w')
      if (array.length === 2) {
        const year = Number(array[0])
        const month = Number(array[1])

        if (!isNaN(year) && !isNaN(month) && month < 54) {
          return text
        }
      }
      return null
    }
  },
  date: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  datetime: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  daterange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  datetimerange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  timerange: {
    formatter: RANGE_FORMATTER,
    parser: RANGE_PARSER
  },
  time: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  month: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  year: {
    formatter: DATE_FORMATTER,
    parser: DATE_PARSER
  },
  number: {
    formatter (value) {
      if (!value) return ''
      return '' + value
    },
    parser (text) {
      let result = Number(text)

      if (!isNaN(text)) {
        return result
      } else {
        return null
      }
    }
  }
}
const PLACEMENT_MAP = {
  left: 'bottom-start',
  center: 'bottom-center',
  right: 'bottom-end'
}

export default {
  props: {
    schema: {
      type: Object,
      default () {
        return {}
      }
    },
    format: String,
    field: Object,
    placeholder: String,
    disabled: Boolean,
    popperClass: String,
    editable: {
      type: Boolean,
      default: false
    },
    align: {
      type: String,
      default: 'left'
    },
    value: {},
    defaultValue: {},
    rangeSeparator: {
      default: ' - '
    }
  },

  data () {
    let vm = this
    return {
      pickerVisible: false,
      currentValue: '',
      unwatchPickerOptions: null,
      visibleOnFocus: true,
      focusWhenHide: true,
      globalClickHandler (evt) {
        if (evt.target === vm.$refs.reference) {
          vm.pickerVisible = true
          return
        }
        if (vm.picker && vm.picker.$el && !vm.picker.$el.contains(evt.target)) {
          vm.focusWhenHide = false
          vm.pickerVisible = false
        }
      }
    }
  },

  watch: {
    pickerVisible (val) {
      if (val && !this.disabled) {
        this.showPicker()
      } else {
        this.hidePicker()
      }
    },
    currentValue (val) {
      if (val) return
      if (this.picker && typeof this.picker.handleClear === 'function') {
        this.picker.handleClear()
      } else {
        this.$emit('input')
      }
    },
    value: {
      immediate: true,
      handler (val) {
        this.currentValue = DateUtils.isDate(val) ? new Date(val) : val
      }
    }
  },

  computed: {
    isRangePicker () {
      return this.type.indexOf('range') > -1
    },

    milliseconds () {
      return this.field ? !!this.field.milliseconds : false
    },

    valueIsEmpty () {
      const val = this.currentValue
      if (Array.isArray(val)) {
        for (let i = 0, len = val.length; i < len; i++) {
          if (val[i]) {
            return false
          }
        }
      } else {
        if (val) {
          return false
        }
      }
      return true
    },

    triggerClass () {
      return this.type.indexOf('time') !== -1 ? 'el-icon-time' : 'el-icon-date'
    },

    selectionMode () {
      if (this.type === 'week') {
        return 'week'
      } else if (this.type === 'month') {
        return 'month'
      } else if (this.type === 'year') {
        return 'year'
      }

      return 'day'
    },

    haveTrigger () {
      if (typeof this.showTrigger !== 'undefined') {
        return this.showTrigger
      }
      return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1
    },

    displayValue: {
      get () {
        const value = this.currentValue
        if (!value) return
        const formatter = (
          TYPE_VALUE_RESOLVER_MAP[this.type] ||
          TYPE_VALUE_RESOLVER_MAP['default']
        ).formatter
        const format = DEFAULT_FORMATS[this.type]
        return formatter(value, this.format || format, this.rangeSeparator)
      },

      set (value) {
        if (value) {
          if (this.millisecond) {
            if (this.isRangePicker && value instanceof Array && value.length === 2) {
              this.picker.value = [new Date(+value[0]), new Date(+value[1])]
            }
            if (!this.isRangePicker) {
              this.picker.value = new Date(+value)
            }
          } else {
            const type = this.type
            const parser = (
              TYPE_VALUE_RESOLVER_MAP[type] ||
              TYPE_VALUE_RESOLVER_MAP['default']
            ).parser
            const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type], this.rangeSeparator)

            if (parsedValue && this.picker) {
              this.picker.value = parsedValue
            }
          }
        } else {
//          this.$emit('input', value)
          this.picker.value = value
        }
//        this.$forceUpdate()
      }
    }
  },
  created () {
    // vue-popper
    this.popperOptions = {
      boundariesPadding: 0,
      gpuAcceleration: false
    }
    this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left
  },

  methods: {
    dateChanged (dateA, dateB) {
      if (Array.isArray(dateA)) {
        let len = dateA.length
        if (!dateB) return true
        while (len--) {
          if (!DateUtils.equalDate(dateA[len], dateB[len])) return true
        }
      } else {
        if (!DateUtils.equalDate(dateA, dateB)) return true
      }

      return false
    },

    handleFocus () {
      if (this.visibleOnFocus) {
        const type = this.type
        if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
          this.pickerVisible = true
        }
      }
    },

    handleKeydown (event) {
      const keyCode = event.keyCode

      // TAB or ESC
      if (keyCode === 9 || keyCode === 27) {
        this.focusWhenHide = false
        this.pickerVisible = false
      }
    },

    hidePicker () {
      if (this.picker) {
        this.picker.resetView && this.picker.resetView()
        this.picker.visible = false
      }
    },

    showPicker () {
      if (!this.picker) {
        this.focusWhenHide = true
        this.mountPicker()
        this.$nextTick(function () {
          this.picker.visible = true
          Utils.off(document, 'click', this.globalClickHandler)
          Utils.on(document, 'click', this.globalClickHandler)
        })

        if (this.currentValue instanceof Date) {
          this.picker.date = new Date(this.currentValue.getTime())
        } else {
          this.picker.value = this.currentValue
        }
        this.picker.resetView && this.picker.resetView()
      }
    },

    mountPicker () {
      this.panel.defaultValue = this.defaultValue || this.currentValue
      this.picker = new Vue(this.panel).$mount()
      this.picker.visible = false
      this.picker.popperClass = this.popperClass
      this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange'
      this.picker.selectionMode = this.selectionMode
      if (this.format) {
        this.picker.format = this.format
      }

      const updateOptions = () => {
        const options = this.field.pickerOptions

        if (options && options.selectableRange) {
          let ranges = options.selectableRange
          const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser
          const format = DEFAULT_FORMATS.timerange

          ranges = Array.isArray(ranges) ? ranges : [ranges]
          this.picker.selectableRange = ranges.map(range => parser(range, format, this.rangeSeparator))
        }

        for (const option in options) {
          if (options.hasOwnProperty(option) &&
              // 忽略 time-picker 的该配置项
              option !== 'selectableRange') {
            this.picker[option] = options[option]
          }
        }
      }
      updateOptions()
      this.unwatchPickerOptions = this.$watch('field.pickerOptions', () => updateOptions(), { deep: true })

      this.$el.appendChild(this.picker.$el)

      this.picker.$on('dodestroy', this.unmountPicker)
      this.picker.$on('pick', (date = '', visible = false) => {
        // do not emit if values are same
        if (!DateUtils.valueEquals(this.value, date)) {
          if (this.milliseconds) {
            if (date instanceof Array) {
              this.$emit('input', [
                date[0] ? date[0].getTime() : null,
                date[1] ? date[1].getTime() : null
              ])
            } else {
              this.$emit('input', date ? date.getTime() : null)
            }
          } else {
            const rangeIdx = this.type.indexOf('range')
            const type = rangeIdx === -1 ? this.type : this.type.substr(0, rangeIdx + 1)
            const formatter = (
              TYPE_VALUE_RESOLVER_MAP[type] ||
              TYPE_VALUE_RESOLVER_MAP['default']
            ).formatter
            const format = DEFAULT_FORMATS[type]

            if (date instanceof Array) {
              this.$emit('input', [
                formatter(date[0], this.format || format),
                formatter(date[0], this.format || format)
              ])
            } else {
              this.$emit('input', formatter(date, this.format || format))
            }
          }
        }
        this.pickerVisible = visible
        this.picker.resetView && this.picker.resetView()
      })

      this.picker.$on('select-range', (start, end) => {
        this.$nextTick(() => {
          if (this.$refs.reference) {
            this.$refs.reference.setSelectionRange(start, end)
            this.$refs.reference.focus()
          }
        })
      })
    },

    unmountPicker () {
      if (this.picker) {
        Utils.off(document, 'click', this.globalClickHandler)
        this.picker.$destroy()
        this.picker.$off()
        if (typeof this.unwatchPickerOptions === 'function') {
          this.unwatchPickerOptions()
        }
        this.picker.$el.parentNode.removeChild(this.picker.$el)
        this.picker = null
        this.pickerVisible = false
        if (this.focusWhenHide) {
          this.$nextTick(() => {
            this.visibleOnFocus = false
            this.$refs.reference.focus()
            this.$nextTick(() => {
              this.visibleOnFocus = true
            })
          })
        }
      }
    }
  }
}
</script>

<style lang="scss" scoped>
  .picker {
    position: relative;
  }
</style>
